//
//  DemoEncrypt.swift
//  swiftDemo
//
//  Created by JIENING ZHANG on 2022/11/22.
//  Copyright © 2022 lovivid. All rights reserved.
//

import UIKit
import CommonCrypto

class DemoEncrypt: NSObject {

    class func md5FromData(_ inputData:Data) -> String {
        
        let inputBytes = [ UInt8 ](inputData);
        let strLen = CUnsignedInt(inputBytes.count)
        let digestLen = Int(CC_MD5_DIGEST_LENGTH)
        let result = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: digestLen)
        CC_MD5(inputBytes, strLen, result)
        let hash = NSMutableString()
        for i in 0 ..< digestLen {
            hash.appendFormat("%02x", result[i])
        }
        result.deinitialize(count: digestLen)
        
        return String(format: hash as String);
    }
    
    class func md5FromString(_ inputStr:String) -> String {
        if let strData = inputStr.data(using: .utf8) {
            return md5FromData(strData);
        }
        return "";
    }
    
    class func md5Demo() {
        let oStr = """
七律·长征
毛泽东
1935年10月
红军不怕远征难，万水千山只等闲
五岭逶迤腾细浪，乌蒙磅礴走泥丸。
金沙水拍云崖暖，大渡桥横铁索寒。
更喜岷山千里雪，三军过后尽开颜。
""";
        print ("oStr=\(oStr) --md5--> \(md5FromString(oStr))");
        
        // 当前可执行程序的全路径
        if let programPath = Bundle.main.executablePath {
            print ("programPath = \(programPath)");
            let fmanager = FileManager.default
            if let fData = fmanager.contents(atPath: programPath) {
                // 提取 二进制 可执行程序 md5 摘要
                print ("fData.count=\(fData.count) --md5--> \(md5FromData(fData))");
            }
        }
    }
    
    class func base64EncodeStr(_ inputStr:String) -> String {
        if let strData = inputStr.data(using: .utf8) {
            return strData.base64EncodedString(options:[.endLineWithLineFeed]);
        }
        return "";
    }
    
    class func base64DecodeStr(_ b64Str:String) -> String {
        if let decodeData = Data(base64Encoded: b64Str, options: .ignoreUnknownCharacters) {
            if let decodeStr = String(data: decodeData, encoding: .utf8) {
                return decodeStr;
            }
        }
        return "";
    }
    
    class func base64Demo() {
        let oStr = """
七律·长征
毛泽东
1935年10月
红军不怕远征难，万水千山只等闲
五岭逶迤腾细浪，乌蒙磅礴走泥丸。
金沙水拍云崖暖，大渡桥横铁索寒。
更喜岷山千里雪，三军过后尽开颜。
""";
        let b64Str = base64EncodeStr(oStr);
        print ("oStr=\(oStr) --base64--> \(b64Str)");
        
        let decodeStr = base64DecodeStr(b64Str);
        print ("decodeStr=\(decodeStr)");
    }
    
    class func urlEncode(_ inputStr:String) -> String {
        var allowedQueryParamAndKey = NSCharacterSet.urlQueryAllowed
        allowedQueryParamAndKey.remove(charactersIn: "!*'\"();:@&=+$,/?%#[]% ")
        return inputStr.addingPercentEncoding(withAllowedCharacters: allowedQueryParamAndKey) ?? "";
    }
    
    class func urlDecode(_ encodeStr:String) -> String {
        return encodeStr.removingPercentEncoding ?? "";
    }
    
    class func urlDemo() {
        let oStr = "https://www.baidu.com/s?tn=50000021_hao_pg&ie=utf-8&word=swift如何做urlencode site:jianshu.com";
        let encode = urlEncode(oStr);
        print ("oStr=\(oStr) --urlEncode--> \(encode)");
        
        let decodeStr = urlDecode(encode);
        print ("decodeStr=\(decodeStr)");
    }
    
    class func createKey(_ password: Data?, _ salt: Data?) -> Data? {
        guard password != nil else {
            return nil;
        }
        guard salt != nil else {
            return nil;
        }
        
        let length = kCCKeySizeAES256
        var status = Int32(0)
        var derivedBytes = [UInt8](repeating: 0, count: length);
        password!.withUnsafeBytes { passwordBytes in
            salt!.withUnsafeBytes { saltBytes in
                status = CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2),  // algorithm
                                              passwordBytes,                // password
                                              password!.count,               // passwordLen
                                              saltBytes,                    // salt
                                              salt!.count,                   // saltLen
                                              UInt32(kCCPRFHmacAlgSHA256),  // prf
                                              UInt32(62233),                // rounds
                                              &derivedBytes,                // derivedKey
                                              length)                       // derivedKeyLen
            }
        }
        guard status == 0 else {
            return nil
        }
        return Data(bytes: UnsafePointer<UInt8>(derivedBytes), count: length)
    }
    
    class func aesEncrypt(_ inputData:Data, _ key:String) -> Data? {
        //key
        guard let keyData = createKey(key.data(using: .utf8), "mcsalt".data(using: .utf8)) else {
            return nil;
        }
        let keyBytes = [ UInt8 ](keyData);
        let keyLength = keyBytes.count;
        
        print ("keyData \(keyData.base64EncodedString())");
        
        //data(input)
        let dataBytes = [ UInt8 ](inputData);
        let dataLength = size_t(dataBytes.count);
        
        //data(output)
        var buffer = Data(count: dataLength + Int(kCCBlockSizeAES128))
        //let bufferBytes = buffer.mutableBytes()
        let bufferLength = size_t(buffer.count)
        //iv
        //let ivBuffer: UnsafePointer<UInt8>? = iv == nil ? nil : iv!.bytes()

        var bytesDecrypted: size_t = 0
        
        let cryptState = buffer.withUnsafeMutableBytes { buffPtr in
            CCCrypt(CCOperation(kCCEncrypt),
                    CCAlgorithm(kCCAlgorithmAES),
                    CCOptions(kCCOptionPKCS7Padding|kCCOptionECBMode),
                    keyBytes,
                    keyLength,
                    dataBytes,  //Initialization vector, optional. Used for Cipher Block Chaining (CBC) mode.
                    dataBytes,
                    dataLength,
                    buffPtr,
                    bufferLength,
                    &bytesDecrypted);
        };
        if cryptState != CCCryptorStatus(kCCSuccess) {
            return nil;
        }
        
        buffer.count = bytesDecrypted;
        return buffer;
    }
    
    class func aesDecrypt(_ encryptData:Data, _ key:String) -> Data? {
        //key
        guard let keyData = createKey(key.data(using: .utf8), "mcsalt".data(using: .utf8)) else {
            return nil;
        }
        let keyBytes = [ UInt8 ](keyData);
        let keyLength = keyBytes.count;
        
        print ("keyData \(keyData.base64EncodedString())");
        
        //data(input)
        let dataBytes = [ UInt8 ](encryptData);
        let dataLength = size_t(dataBytes.count);
        
        //data(output)
        var buffer = Data(count: dataLength + Int(kCCBlockSizeAES128))
        //let bufferBytes = buffer.mutableBytes()
        let bufferLength = size_t(buffer.count)
        //iv
        //let ivBuffer: UnsafePointer<UInt8>? = iv == nil ? nil : iv!.bytes()

        var bytesDecrypted: size_t = 0
        
        let decryptState = buffer.withUnsafeMutableBytes { buffPtr in
            CCCrypt(CCOperation(kCCDecrypt),
                    CCAlgorithm(kCCAlgorithmAES),
                    CCOptions(kCCOptionPKCS7Padding|kCCOptionECBMode),
                    keyBytes,
                    keyLength,
                    dataBytes,  //Initialization vector, optional. Used for Cipher Block Chaining (CBC) mode.
                    dataBytes,
                    dataLength,
                    buffPtr,
                    bufferLength,
                    &bytesDecrypted);
        };
        if decryptState != CCCryptorStatus(kCCSuccess) {
            return nil;
        }
        
        buffer.count = bytesDecrypted;
        return buffer;
    }
    
    class func aesDemo() {
        let oStr = """
七律·长征
毛泽东
1935年10月
红军不怕远征难，万水千山只等闲
五岭逶迤腾细浪，乌蒙磅礴走泥丸。
金沙水拍云崖暖，大渡桥横铁索寒。
更喜岷山千里雪，三军过后尽开颜。
""";
        if let strData = oStr.data(using: .utf8) {
            if let encryptData = aesEncrypt(strData, "1893-12-26") {
                print ("encryptData = \(encryptData.base64EncodedString())");
                
                if let decryptData = aesDecrypt(encryptData, "1893-12-26") {
                    print ("decryptData = \(decryptData.base64EncodedString())");
                    if let decryptStr = String(data: decryptData, encoding: .utf8) {
                        print ("decryptStr = \(decryptStr)");
                    } else {
                        print ("fail to get string from decryptStr");
                    }
                } else {
                    print ("fail to aes Decrypt");
                }
            } else {
                print ("fail to aes Encrypt");
            }
        }
        
    }
    
    class func demoLogic() {
        md5Demo();
        
        base64Demo();
        
        urlDemo();
        
        aesDemo();
    }
}
